package com.lipaluma.field.adaptor;

import static org.joox.JOOX.$;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.joox.Match;
import org.w3c.dom.Element;

import com.lipaluma.Xml;
import com.lipaluma.annotations.adaptors.Adapt;
import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.adaptor.impl.StringReplaceAdaptor;
import com.lipaluma.field.adaptor.impl.TrimAdaptor;
import com.lipaluma.field.adaptor.impl.TruncateAdaptor;
import com.lipaluma.xml.adaptor.XmlAdaptor;
import com.lipaluma.xml.mapping.XmlFieldMapping;

public class AdaptorFactory {

	private static List<Class<? extends Adaptor>> defaultAdaptors = new ArrayList<Class<? extends Adaptor>>();
	
	static {
		defaultAdaptors.addAll(Arrays.asList(TrimAdaptor.class, StringReplaceAdaptor.class, TruncateAdaptor.class));
	}

	
	public static void addDefaultAdaptor(Class<? extends Adaptor> adaptor) {
		defaultAdaptors.add(adaptor);
	}
	public static void redefineDefaultAdaptors(List<Class<? extends Adaptor>> adaptors) {
		defaultAdaptors = new ArrayList<Class<? extends Adaptor>>();
		defaultAdaptors.addAll(adaptors);
	}
	
	public static List<Adaptor> getDefaultAdaptors() {
		return instantiateAllAdaptors(defaultAdaptors);
	}
	
	public static Adaptor instantiateNewAdaptor(Class<? extends Adaptor> clazz) {
		try {
			Adaptor instance = clazz.newInstance();
			return instance;
		} catch (InstantiationException e) {
			throw new IllegalStateException("The Validator "+clazz+" must have an empty accessible constructor");
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("The Validator "+clazz+" must have an empty accessible constructor");
		}

	}

	/**
	 * create a validatorby by instantiating clazzname
	 * @param clazzname the class name of the validator instantiate
	 * @return a validator
	 */
	public static Adaptor instantiateNewAdaptor(String clazzname) {
		try {
			return (Adaptor)Class.forName(clazzname).newInstance();
		} catch (ClassCastException ex) {
			throw new CsvParserException("The class "+clazzname+" is not a Validator.", ex);
		} catch (InstantiationException ex) {
			throw new CsvParserException("The class "+clazzname+" can't be instanciated.", ex);
		} catch (IllegalAccessException ex) {
			throw new CsvParserException("The class "+clazzname+" is not accessible.", ex);
		} catch (ClassNotFoundException ex) {
			throw new CsvParserException("The class "+clazzname+" is not found.", ex);
		}
	}

	public static List<Adaptor> instantiateAllAdaptors(Class<? extends Adaptor> ... classes) {
		return instantiateAllAdaptors(Arrays.asList(classes));
	}

	public static List<Adaptor> instantiateAllAdaptors(Collection<Class<? extends Adaptor>> classes) {
		List<Adaptor> result = new ArrayList<Adaptor>();		
		for(Class<? extends Adaptor> adaptorClass : classes) {
			result.add(instantiateNewAdaptor(adaptorClass));
		}
		return result;
	}

	public static List<Adaptor> instantiateAllAdaptors(String ... classes) {
		List<Adaptor> result = new ArrayList<Adaptor>();		
		for(String adaptorClass : classes) {
			result.add(instantiateNewAdaptor(adaptorClass));
		}
		return result;
	}

	
	public static ChainAdaptor createNewChainAdaptor(Context context, Field field) {
		ChainAdaptor chainAdaptor = new ChainAdaptor();

		List<Adaptor> adaptors;
		Adapt annotation = field.getAnnotation(Adapt.class);
		if(annotation != null && annotation.doInOrder().length != 0) {
			adaptors = instantiateAllAdaptors(annotation.doInOrder());
		} else {
			adaptors = getDefaultAdaptors();
		}
		
		for (Adaptor adaptor : adaptors) {
			if(adaptor.init(context, field))
				chainAdaptor.push(adaptor);
		}
		return chainAdaptor;
	}

	public static ChainAdaptor createNewChainAdaptor(Context context, Field field, XmlFieldMapping mapping) {
		ChainAdaptor chainAdaptor = new ChainAdaptor();
		if(mapping.isTrim())
			chainAdaptor.push(new TrimAdaptor());
		for (XmlAdaptor xmlAdaptor : mapping.getAdaptors()) {
			Adaptor adaptor = instantiateNewAdaptor(xmlAdaptor.with());
			if(adaptor.init(context, field, xmlAdaptor))
				chainAdaptor.push(adaptor);
		}
		return chainAdaptor;
	}
	
	public static String[] extractAdaptorClassesFromElement(Element element) {
		List<String> adaptors = new ArrayList<String>();
		Match adaptorElements = $(element).child(Xml.DO_IN_ORDER).children(Xml.ADAPTOR);
		if(!adaptorElements.isEmpty()) {
			for (Match adaptor : adaptorElements.each()) {
				adaptors.add(adaptor.text());
			}
		}
		return adaptors.toArray(new String[0]);
	}
}
